package com.nbsaas.boot;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * CustomFeistelNumHandle -- 完全自设计的可逆算法实现（不调用成熟加密库）
 *
 * 说明：
 * - 输出为 32 个小写 hex 字符（表示 128-bit）
 * - 无外部依赖，纯用整数/位操作
 * - 注意：这是自创算法，应在生产前进行第三方密码学审计
 */
public class CustomFeistelNumHandle implements NumHandle {

    private static final int BLOCK_BYTES = 16;   // 128 bits
    private static final int HALF_BYTES = 8;     // 64 bits halves
    private static final int ROUNDS = 24;        // 轮数（可调整）
    private final long[] roundAdd;   // per-round additive constant (64-bit)
    private final long[] roundXor;   // per-round xor constant (64-bit)
    private final byte[] prefix12;   // first 12 bytes of plaintext (secret-derived)

    /**
     * 构造器：传入 secret（任意字符串），实例对该 secret 产生一套轮常数
     */
    public CustomFeistelNumHandle(String secret) {
        if (secret == null) throw new IllegalArgumentException("secret required");
        // 从 secret 派生 PRNG 种子
        long seed = deriveSeedFromSecret(secret);
        // 用内部 PRNG (xorshift128plus-like) 生成 needed constants
        XorShift128Plus prg = new XorShift128Plus(seed, seed ^ 0x9E3779B97F4A7C15L);

        roundAdd = new long[ROUNDS];
        roundXor = new long[ROUNDS];
        for (int i = 0; i < ROUNDS; i++) {
            roundAdd[i] = prg.nextLong();
            roundXor[i] = prg.nextLong();
        }
        // prefix12 从 PRG 进一步得到 12 字节固定前缀
        prefix12 = new byte[12];
        for (int i = 0; i < 12; i++) {
            prefix12[i] = (byte) (prg.nextLong() >>> (i * 5 & 63)); // mix bits
        }
    }

    // ----------------- NumHandle 接口实现 -----------------
    @Override
    public String encode(Integer num) {
        if (num == null) throw new IllegalArgumentException("num required");
        byte[] plain = makePlainBlock(num);
        byte[] cipher = feistelEncrypt(plain);
        return toHexLower(cipher);
    }

    @Override
    public Integer decode(String key) {
        if (key == null) throw new IllegalArgumentException("key required");
        if (key.length() != 32) throw new IllegalArgumentException("key must be 32 hex chars");
        byte[] cipher = fromHex(key);
        byte[] plain = feistelDecrypt(cipher);
        // extract last 4 bytes -> int (big-endian)
        int recovered = ByteBuffer.wrap(Arrays.copyOfRange(plain, 12, 16)).getInt();
        return recovered;
    }

    // ----------------- 明文构造 -----------------
    // 16 bytes: [prefix12 (12 bytes)] [int32 (4 bytes, big-endian)]
    private byte[] makePlainBlock(int num) {
        byte[] block = new byte[BLOCK_BYTES];
        System.arraycopy(prefix12, 0, block, 0, 12);
        byte[] intBytes = ByteBuffer.allocate(4).putInt(num).array();
        System.arraycopy(intBytes, 0, block, 12, 4);
        return block;
    }

    // ----------------- Feistel 网络 -----------------
    private byte[] feistelEncrypt(byte[] plain) {
        if (plain.length != BLOCK_BYTES) throw new IllegalArgumentException("plain block size");
        long L = bytesToLong(Arrays.copyOfRange(plain, 0, HALF_BYTES));
        long R = bytesToLong(Arrays.copyOfRange(plain, HALF_BYTES, BLOCK_BYTES));

        for (int r = 0; r < ROUNDS; r++) {
            long f = roundFunction(R, r);
            long newL = R;
            long newR = L ^ f;
            L = newL;
            R = newR;
        }

        byte[] out = new byte[BLOCK_BYTES];
        System.arraycopy(longToBytes(L), 0, out, 0, 8);
        System.arraycopy(longToBytes(R), 0, out, 8, 8);
        return out;
    }

    private byte[] feistelDecrypt(byte[] cipher) {
        if (cipher.length != BLOCK_BYTES) throw new IllegalArgumentException("cipher block size");
        long L = bytesToLong(Arrays.copyOfRange(cipher, 0, HALF_BYTES));
        long R = bytesToLong(Arrays.copyOfRange(cipher, HALF_BYTES, BLOCK_BYTES));

        for (int r = ROUNDS - 1; r >= 0; r--) {
            long f = roundFunction(L, r);  // note reversed roles for decryption
            long newR = L;
            long newL = R ^ f;
            L = newL;
            R = newR;
        }

        byte[] out = new byte[BLOCK_BYTES];
        System.arraycopy(longToBytes(L), 0, out, 0, 8);
        System.arraycopy(longToBytes(R), 0, out, 8, 8);
        return out;
    }

    // ----------------- 自定义轮函数（核心） -----------------
    private long roundFunction(long half, int roundIndex) {
        // 设计目标：简单、可计算、非线性、且依赖 round constants
        long kAdd = roundAdd[roundIndex];
        long kXor = roundXor[roundIndex];

        // step1: mix with additive constant
        long x = half + kAdd; // mod 2^64

        // step2: non-linear multiply with odd constant derived from kXor
        long oddConst = (kXor | 1L); // ensure odd
        x = x * oddConst; // multiply mod 2^64

        // step3: rotate and xor with shifted versions for diffusion
        x ^= Long.rotateLeft(x, 13);
        x ^= Long.rotateRight(x, 17);

        // step4: further mix using a small bijection: multiply by another odd constant, then fold
        long fold = (kAdd ^ kXor) | 1L;
        x = x * fold;

        // step5: finally xor with a rotated version of roundIndex-derived bits
        long rBits = ((long) (roundIndex + 0x9E3779B9)) << 32 ^ ((long) roundIndex * 0x7feb352d);
        x ^= Long.rotateLeft(rBits ^ kXor, (roundIndex & 63));

        // return x as 64-bit output
        return x;
    }

    // ----------------- helper PRG and seed derivation -----------------
    // Derive a 64-bit seed from secret string using simple fold/mix (no external hash)
    private static long deriveSeedFromSecret(String secret) {
        byte[] s = secret.getBytes(StandardCharsets.UTF_8);
        long a = 0x0123456789ABCDEFL;
        for (int i = 0; i < s.length; i++) {
            a ^= ((long) s[i] & 0xFFL) << ((i * 5) & 63);
            a *= 0x9E3779B97F4A7C13L; // golden-ish constant
            a ^= (a >>> 33);
        }
        // Final avalanche
        a ^= (a << 21);
        a ^= (a >>> 35);
        a *= 0xC2B2AE3D27D4EB4FL;
        return a;
    }

    // Simple xorshift128+ like PRNG (lightweight, deterministic)
    private static final class XorShift128Plus {
        private long s0, s1;

        XorShift128Plus(long seed0, long seed1) {
            if (seed0 == 0 && seed1 == 0) {
                seed0 = 0xdeadbeefcafebabeL;
                seed1 = 0x1234567890abcdefL;
            }
            this.s0 = seed0;
            this.s1 = seed1;
            // warm up
            for (int i = 0; i < 10; i++) nextLong();
        }

        long nextLong() {
            long x = s0;
            long y = s1;
            s0 = y;
            x ^= x << 23;
            x ^= x >>> 17;
            x ^= y ^ (y >>> 26);
            s1 = x;
            return x + y;
        }
    }

    // ----------------- byte/long helpers -----------------
    private static long bytesToLong(byte[] b) {
        ByteBuffer bb = ByteBuffer.wrap(b);
        return bb.getLong();
    }

    private static byte[] longToBytes(long v) {
        ByteBuffer bb = ByteBuffer.allocate(8);
        bb.putLong(v);
        return bb.array();
    }

    // ----------------- hex helpers -----------------
    private static String toHexLower(byte[] b) {
        StringBuilder sb = new StringBuilder(b.length * 2);
        for (byte x : b) {
            sb.append(String.format("%02x", x));
        }
        return sb.toString();
    }

    private static byte[] fromHex(String s) {
        int len = s.length();
        byte[] out = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            out[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return out;
    }

    // ----------------- demo main -----------------
    public static void main(String[] args) {
        // demo
        CustomFeistelNumHandle handler = new CustomFeistelNumHandle("my-unique-secret-2025!");
        int[] tests = {0, 1, -1, 12345678, Integer.MAX_VALUE, Integer.MIN_VALUE, 20251021};
        for (int t : tests) {
            String token = handler.encode(t);
            int recovered = handler.decode(token);
            System.out.printf("num=%d -> token=%s -> recovered=%d (ok=%b)%n",
                    t, token, recovered, (t == recovered));
        }
    }
}
